﻿using System;

using System.Collections.Generic;
using System.Text;
using Silvermoon.Controls.Classes;
using System.Drawing;
using Silvermoon.Animations;

namespace Silvermoon.Controls
{
    /// <summary>
    /// Control that contains collection of T called chunks to show always one chunk completely and enables horicontal scrolling to select another chunk.
    /// </summary>
    /// <typeparam name="T">The type of chunk to use.</typeparam>
    public class Slider<T> : NotifyEventControl, IScrollEventBehavior where T : Control
    {
        const int MaxLevel = 1000;
        #region Fields

        private bool scroll = false;
        private int offset0 = 0;
        private ControlCollection subControls;
        private ControlCollection<T> chunks;
        private int offset;
        private Animation gotoDefaultAnim;
        private int beginChunk;


        #endregion
        #region ctor/dtor

        public Slider()
        {
            AutoStop = true;
            subControls = new ControlCollection(this);
            chunks = new ControlCollection<T>(this);
        }

        public override void Dispose()
        {
            if (gotoDefaultAnim != null)
            {
                gotoDefaultAnim.Abort();
            }
            base.Dispose();
        }

        #endregion
        #region Properties

        protected internal override IList<Control> SubControls { get { return subControls; } }

        public int Offset { get { return offset; } set { value = ValidateOffset(value); if (value != offset) { OnOffsetChange(value); } } }

        /// <summary>
        /// Gets or sets the selected chunk. Note that this is a calculated value.
        /// </summary>
        public int SelectedChunk
        {
            get { return GetSelectedChunk(); }
            set
            {
                int offset = value * Width;
                if (this.offset != offset)
                {
                    this.Offset = offset;
                }
            }
        }

        public IList<T> Chunks { get { return chunks; } }

        /// <summary>
        /// Gets or sets whether scrolling will select only the next or previous chunk, if set to true, otherwhise it will scroll through chunks until kinetic scrolling finishes.
        /// </summary>
        public bool AutoStop { get; set; }

        #endregion
        #region Methods

        private int ValidateOffset(int offset)
        {
            int max = Chunks.Count * Width;
            while (offset > max) offset -= max;
            while (offset < -max) offset += max;
            return offset;
        }

        protected virtual void OnOffsetChange(int offset)
        {
            this.offset = offset;
            Invalidate(InvalidationFlag.LayoutAppearance);
        }

        protected override void OnLayout()
        {
            if (CheckFlag(InvalidationFlag.LayoutAppearance | InvalidationFlag.Size))
            {
                SubControls.Clear();
                int count = Chunks.Count;
                int max = Width * count;
                int offset = this.offset % max;
                if (offset < 0) offset += max;
                int index = offset / Width;
                if (index > count) index -= count;
                else if (index < 0) index += count;
                int left = index * Width - offset;
                if (left > max) left -= max;
                while (index < count)
                {
                    if (left > Width) break;
                    Control chunk = Chunks[index];
                    chunk.Bounds = new Rectangle(left, 0, Width, Height);
                    subControls.Add(chunk);
                    left += Width;
                    index++;
                    if (count == 1) break;
                    if (index >= count) index = 0;
                }
            }

            base.OnLayout();
        }

        private void GoToValidOffset()
        {
            // this looks reduntant, but setting offset also checks the boundaries and on demand changes the values, so the following command is useful:
            Offset = Offset;

            int offset = Offset;
            StopOffsetAnim();
            if ((offset % Width) != 0)
            {
                int w = Width;
                int count = chunks.Count;
                int max = w * count;
                if (offset < 0) offset += max;
                offset = offset % (w * count);
                int index = ((offset + w / 2) / w);
                int targetOffset = index * w;
                if ((offset - targetOffset) > w) offset -= max;
                gotoDefaultAnim = new Animation(Animation.DefaultDuration, offset, targetOffset, 0);
                gotoDefaultAnim.Progress += new EventHandler<AnimEventArgs>(OnOffsetAnimProgress);
                gotoDefaultAnim.InvokeAsync();
            }
        }


        protected virtual int GetSelectedChunk()
        {
            int w = Width;
            if (w==0) w = 1;
            int offset = Offset - w / 2;
            int count = chunks.Count;
            if ((offset % Width) != 0)
            {
                int max = w * count;
                if (offset < 0) offset += max;
                offset = offset % (w * count);
                int index = ((offset + w / 2) / w);
                int targetOffset = index * w;
                if ((offset - targetOffset) > w) offset -= max;
            }
            int result = w > 0 ? (offset / w) % count : 0;
            if (result < 0) result += count;
            return result;
        }

        #endregion
        #region IScrollEventBehavior Members

        public virtual void OnScroll(ScrollEventArgs e)
        {
            if (!e.Handled && scroll)
            {
                if (AutoStop && !e.IsMouseDown)
                {
                    if (beginChunk != SelectedChunk)
                    {
                        e.Cancel = true;
                    }
                }
                e.Handled = true;
                int dx = e.Dx;
                Offset = offset0 - dx;
            }
        }

        public virtual void OnBeginScrollData(BeginScrollEventArgs e)
        {
            beginChunk = SelectedChunk;
            scroll = false;
            if (!e.Handled)
            {
                if ((e.ScrollDirection & (ScrollDirection.Left | ScrollDirection.Right)) != 0)
                {
                    StopOffsetAnim();

                    //only scroll when there are at least 2 chunks available, otherwise cancel scolling:
                    if (Chunks.Count > 1)
                    {
                        scroll = true;
                        offset0 = Offset;
                    }
                    else e.Cancel = true;
                    e.Handled = true;
                }
            }
        }

        public virtual void OnEndScrollData(EndScrollEventArgs e)
        {
            if (scroll) GoToValidOffset();
            scroll = false;
        }

        void OnOffsetAnimProgress(object sender, AnimEventArgs e)
        {
            // we do not check the valid Offset range here and go direct to change the offset value:
            OnOffsetChange(e.Value);
        }


        private void StopOffsetAnim()
        {
            if (gotoDefaultAnim != null)
            {
                gotoDefaultAnim.Abort();
            }
        }

        #endregion
    }

    /// <summary>
    /// Control that contains collection of Controls called chunks to show always one chunk completely and enables horicontal scrolling to select another chunk.
    /// </summary>
    public class Slider : Slider<Control> { }
}
